package preprocessing;

import weka.core.Attribute;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSink;
import weka.core.converters.ConverterUtils.DataSource;
import java.util.Random;

/*
实现代码模块化，运用低秩矩阵分解方法进行矩阵分解，参考：https://www.cnblogs.com/wuliytTaotao/p/10814770.html
使用时修改的地方：
k（24行）：分解后的矩阵的大小
itertions（24行）：迭代次数
alpha（31行）：参数
beta（31行）:参数
修改准确度主要调整alpha和beta值
source（188行）:读取arff数据的路径
newsource(207行)：存储arff类型数据的路径
 */

public class Matrix_completion{

    public static int n,m,k=7,itertions=1000;
    /*
    m:需要分解的矩阵的行数
    n:需要分解矩阵的列数
    k:矩阵分解后的两个矩阵分别对应的行数和列数
    itertions:迭代次数
    */
    public static double mean,alpha=0.0005,beta=0.0005,prediction;
    /*
    mean:去除缺失值后的均值
    alpha，beta:参数
    prediction:预测的元素值
     */
    public static double[][] set_parameter_U() { //分解矩阵U的初始化
        double [][]U=new double[m][k];
        Random r = new Random();
        double var=Math.sqrt(1.0/k); //方差
        for (int i = 0; i < k; i++) {
            for (int U_j = 0; U_j < m; U_j++) {
                U[U_j][i] = var * r.nextGaussian();//高斯随机数
            }
        }
        return U;
    }

    public static double[][] set_parameter_V() { //分解矩阵V的初始化
        double [][]V=new double[n][k];
        Random r = new Random();
        double var=Math.sqrt(1.0/k); //方差
        for (int i = 0; i < k; i++) {
            for(int V_j=0;V_j<n;V_j++) {
                V[V_j][i]=var * r.nextGaussian(); //高斯随机数
            }
        }
        return V;
    }

    public static double[][] prepossess(Instances instances){ //数据处理，计算均值以及提取出instances的数值
        int len=m,attribute=n,i,j;
        double sum=0,statistic=0;
        double [][]data=new double[m][n];
        for(i=0;i<attribute;i++){
            for(j=0; j<len;j++){
                if(Double.isNaN(instances.instance(j).value(i))!=true) {
                    data[j][i] = instances.instance(j).value(i);
                    sum += data[j][i];
                    statistic+=1;
                }else{
                    data[j][i]=-1;
                }
            }
        }
        mean=sum/statistic;
        return data;
    }

    public static double[][] train(double [][]data,double [][]U,double [][]V){ //训练函数
        double toal_error,error,b=mean,sum;
        double []b_u=new double[m];
        double []b_v=new double[n];
        double [][]predicted=new double[m][n]; //预测后的矩阵的值
        for(int parameter=0;parameter<itertions;parameter++){
            for(int i=0;i<m;i++){
                for(int j=0;j<n;j++){
                    if(data[i][j]!=-1) {
                        sum = Matrix_multiplication(U[i], V[j]);
                        prediction = b + b_u[i] + b_v[j] + sum;
                        error = data[i][j] - prediction;
                        b_u[i]+=alpha*(2*error-beta*b_u[i]);
                        b_v[j]+=alpha*(2*error-beta*b_v[j]);
                        for(int integer=0;integer<k;integer++){
                            U[i][integer]=U[i][integer]+Math.round((alpha*(2*error*V[j][integer]-beta*U[i][integer]))*Math.pow(10,50))/1.0/Math.pow(10,50);
                            V[j][integer]=V[j][integer]+Math.round((alpha*(2*error*U[i][integer]-beta*V[j][integer]))*Math.pow(10,50))/1.0/Math.pow(10,50);
                        }
                    }
                }
            }
            toal_error=square_error(data,predicted,U,V,b_u,b_v); //总误差
            if((parameter+1)%5==0){
                System.out.println("iteration:"+(parameter+1)+"   total_error:"+toal_error);
            }
        }
        return predicted;
    }

    public static double square_error(double [][]data,double [][]predicted,double [][]U,double [][]V,double []b_u,double []b_v){ //计算误差并对预测矩阵赋值
        double error=0;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                predicted[i][j] = mean + b_u[i] + b_v[j] +  Matrix_multiplication(U[i], V[j]);
            }
        }
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(data[i][j]!=-1) {
                    error += Math.pow(data[i][j] - predicted[i][j], 2);
                }
            }
        }
        return error;
    }


    public static double Matrix_multiplication(double[]U, double []V){ //向量相乘的实现
        double sum=0;
        for(int integer=0;integer<k;integer++){
            sum=Math.round((sum+U[integer]*V[integer])*Math.pow(10,50))/1.0/Math.pow(10,50);
        }
        return sum;
    }

    public static void printAttribute(Instances instances) { //打印instances的数值
        int numOfAttributes = instances.numAttributes();
        for(int i = 0; i < numOfAttributes ;++i)
        {
            Attribute attribute = instances.attribute(i);
            System.out.print(attribute.name() + " ");
        }
        System.out.println();
        //打印实例
        int numOfInstance = instances.numInstances();
        for(int i = 0; i < numOfInstance; ++i)
        {
            Instance instance = instances.instance(i);
            System.out.print(instance.toString() + " " + "\n");
        }
    }

    public static double[][] predicted_replace(double [][]data,double [][]predicted){ //将data中缺失值补全
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(data[i][j]==-1){
                    if(predicted[i][j]<0){
                        predicted[i][j]=0;
                    }
                    data[i][j]=predicted[i][j];
                }
            }
        }
        return data;
    }

    static boolean SetValue(Instances instances,double [][]data){ //对instances对象赋值
        for(int i=0;i<n;i++) {
            if(instances.attributeStats(i).numericStats==null){
                double length=instances.attributeStats(i).distinctCount-1;
                for(int j=0;j<m;j++){
                    data[j][i]=Math.round(data[j][i]);
                    if(data[j][i]>length){
                        data[j][i]=length;
                    }
                    instances.instance(j).setValue(i, data[j][i]);
                }
            }else {
                for (int j = 0; j < m; j++) {
                    data[j][i]=Math.round(data[j][i]*10)/10.0;
                    instances.instance(j).setValue(i, data[j][i]);
                }
            }
        }
        return true;
    }

    public static void main(String[] args) throws Exception {
        DataSource source = new DataSource("/home/tang/appdata/weka-3-8-4/data/labor.arff"); //读取数据的路径
        Instances instances= source.getDataSet();
        boolean success;
        double [][]data;
        double [][]predicted;
        double [][]U;
        double [][]V;
        n=instances.numAttributes()-1; //去除arff的class列
        m=instances.numInstances();
        U=set_parameter_U(); //设置分解矩阵U初值
        V=set_parameter_V(); //设置分接矩阵V的初值
        data=prepossess(instances); //将Instances数值装换成二位矩阵data，并对缺失值赋值为-1
        predicted=train(data,U,V); //U,V矩阵相乘后的预测值矩阵
        data=predicted_replace(data,predicted); //对data中缺失值进行填补
        success=SetValue(instances,data); //instances缺失值补全
        if(success==true) {
            System.out.println("数据缺失值处理完成,结果如下：");
            printAttribute(instances);
        }
        DataSink newsource = new DataSink("/home/tang/test.arff"); //存储arff数据路径
        newsource.write(instances);
    }
}
